package com.nulldev.util.graphics.renderIt.renderer.global;

import java.awt.Color;
import java.util.concurrent.atomic.AtomicReference;

import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.RandomUtil;
import com.nulldev.util.data.FastMath.FastMath;
import com.nulldev.util.data.Images.ColorUtil;

public class rColor {

	private static final com.nulldev.util.data.FastMath.Math Math = FastMath.get();

	@SuppressWarnings("static-access")
	public static enum rColor_Type {
		/* TODO: Redesign this to support other encodings like RGB565 and ARGB565 */
		RGB_2BIT(Math.pow(2, 3)),
		ARGB_2BIT(Math.pow(2, 4)),
		RGB_4BIT(Math.pow(4, 3)),
		ARGB_4BIT(Math.pow(3, 4)),
		RGB_8BIT(Math.pow(8, 3)),
		ARGB_8BIT(Math.pow(8, 4)),
		RGB_10BIT(Math.pow(10, 3)),
		ARGB_10BIT(Math.pow(10, 4)),

		;

		protected final long size;

		private rColor_Type(final double limit) {
			this.size = (long) limit - 1;
		}

		private rColor_Type(final long limit) {
			this.size = limit - 1;
		}
	}

	public static final rColor BLACK = new rColor(0, 0, 0);
	public static final rColor black = BLACK;
	public static final rColor WHITE = new rColor(255, 255, 255);
	public static final rColor white = WHITE;
	public static final rColor PURPLE = new rColor(128, 0, 128);

	private final rColor_Type type;
	private volatile boolean locked;
	private final long safeSize;
	private final long MINIMUM;
	private volatile long color = 0;
	private final AtomicReference<Color> awtColorCache = new AtomicReference<Color>();

	public rColor(final rColor_Type type) {
		this.type = type;
		this.safeSize = type.size;
		this.MINIMUM = -type.size;
	}

	public rColor(final long rgb) {
		this.type = rColor_Type.RGB_8BIT;
		this.safeSize = 16777215;
		this.MINIMUM = -16777215;
		colorBoundsCheck(rgb);
		this.color = rgb;
		this.locked = true;
	}

	public rColor(final long rgb, final boolean useCSS) {
		this.type = rColor_Type.RGB_8BIT;
		this.safeSize = 16777215;
		this.MINIMUM = -16777215;
		if (useCSS) {
			this.color = rgb;
			this.locked = true;
		} else {
			colorBoundsCheck(rgb);
			this.color = rgb;
			this.locked = true;
		}
	}

	public rColor(final rColor_Type type, final int rgb) {
		this.type = type;
		this.safeSize = type.size;
		this.MINIMUM = 0;
		colorBoundsCheck(rgb);
		this.color = rgb;
		this.locked = true;
	}

	public rColor(final int r, final int g, final int b) {
		if (r < 0 || r > 255)
			throw new IllegalArgumentException("Illegal value 'r'!");
		if (g < 0 || g > 255)
			throw new IllegalArgumentException("Illegal value 'g'!");
		if (b < 0 || b > 255)
			throw new IllegalArgumentException("Illegal value 'b'!");
		this.type = rColor_Type.RGB_8BIT;
		this.MINIMUM = 0;
		this.safeSize = type.size;
		this.set8(r, g, b);
	}

	public rColor(final Color c) {
		this.locked = true;
		this.type = rColor_Type.ARGB_8BIT;
		this.MINIMUM = 0;
		this.safeSize = this.type.size;
		this.color = c.getRGB();
	}

	private void colorBoundsCheck(final long color) {
		if (color < MINIMUM || color > safeSize) {
			throw new IllegalArgumentException("Color bound failed! [color: " + color + ", safeSize: " + this.safeSize + "]");
		}
	}

	public void set(final long color) {
		if (this.locked)
			return;
		colorBoundsCheck(color);
		this.color = color;
		this.awtColorCache.set(null);
	}

	public void set8(final int r, final int g, final int b) {
		if (this.locked)
			return;
		if (r < 0 || r > 255)
			throw new IllegalArgumentException("Illegal value 'r'!");
		if (g < 0 || g > 255)
			throw new IllegalArgumentException("Illegal value 'g'!");
		if (b < 0 || b > 255)
			throw new IllegalArgumentException("Illegal value 'b'!");
		this.color = 65536 * r + 256 * g + b;
		this.awtColorCache.set(null);
	}

	public void set8A(final int r, final int g, final int b, final int a) {
		if (this.locked)
			return;
		if (r < 0 || r > 255)
			throw new IllegalArgumentException("Illegal value 'r'!");
		if (g < 0 || g > 255)
			throw new IllegalArgumentException("Illegal value 'g'!");
		if (b < 0 || b > 255)
			throw new IllegalArgumentException("Illegal value 'b'!");
		if (a < 0 || a > 255)
			throw new IllegalArgumentException("Illegal value 'a'!");
		this.color = 65536 * r + 256 * g + b + a;
		this.awtColorCache.set(null);
	}

	public Color toAWTColor() {
		if (this.awtColorCache.get() == null) {
			switch (this.type) {
				case ARGB_8BIT:
				case RGB_8BIT:
					return this.awtColorCache.updateAndGet((e) -> {
						return new Color((int) this.color);
					});
				default:
					throw new UnsupportedOperationException("Cannot export '" + type + "' to AWT!");
			}
		} else {
			return this.awtColorCache.get();
		}
	}

	public static enum ColorFlag {
		INT_RED,
		INT_GREEN,
		INT_BLUE,
		INT_ALPHA,

		FLOAT_RED,
		FLOAT_GREEN,
		FLOAT_BLUE,
		FLOAT_ALPHA
	}

	public Object getFlag(final ColorFlag flag) {
		switch (flag) {
			/* TYPE: INT */
			case INT_RED: {
				return (color & 0xFF000000) >> 24;
			}
			case INT_GREEN: {
				return (color & 0xFF000000) >> 16;
			}
			case INT_BLUE: {
				return (color & 0xFF000000) >> 8;
			}
			case INT_ALPHA: {
				return (color & 0xFF000000);
			}
			/* TYPE: FLOAT */
			case FLOAT_RED:
				return (float) (((color & 0xFF000000) >> 24) / 255);
			case FLOAT_GREEN:
				return (float) (((color & 0xFF000000) >> 16) / 255);
			case FLOAT_BLUE:
				return (float) (((color & 0xFF000000) >> 8) / 255);
			case FLOAT_ALPHA:
				return (float) (((color & 0xFF000000)) / 255);
			/* TYPE: MISC. */
			default:
				return null;
		}
	}

	public boolean isLocked() {
		return this.locked;
	}

	public static rColor as(final Color c) {
		return new rColor(c);
	}

	public static rColor from(final int color) {
		return new rColor(color);
	}

	@Override
	public String toString() {
		return "rColor[hash=" + MathUtil.toHex(super.hashCode()) + ",type=" + this.type + ",value=" + this.color + "]";
	}

	public static rColor random() {
		return new rColor((int) RandomUtil.randLong(0, 16777215));
	}

	public static rColor rainbow() {
		return new rColor(ColorUtil.rainbow(0, 1.0f).getRGB());
	}
}
